package com.wyc.jt808.handler;

import com.wyc.jt808.codec.MsgDecoder;
import com.wyc.jt808.common.CommonConstant;
import com.wyc.jt808.common.TPMSConsts;
import com.wyc.jt808.dao.DqJt808LocationInfoMapper;
import com.wyc.jt808.dao.DqVehicleInfoMapper;
import com.wyc.jt808.dto.VehicleInDataTimeDto;
import com.wyc.jt808.model.DqJt808LocationInfo;
import com.wyc.jt808.server.SessionManager;
import com.wyc.jt808.utils.JT808ProtocolUtils;
import com.wyc.jt808.vo.PackageData;
import com.wyc.jt808.vo.Session;
import com.wyc.jt808.vo.req.LocationInfoUploadMsg;
import com.wyc.jt808.vo.req.TerminalAuthenticationMsg;
import com.wyc.jt808.vo.req.TerminalRegisterMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * 在这里我使用spring容器管理TCPServerHandler，所以默认是单例的；
 * 当有多个客户端连接时会行程多条管线，由于默认每条管线(Channel)创建的ChannelPipeline管理自己的EchoServerHandler实例
 * DefaultChannelPipeline类中的checkMultiplicity()方法会将实例状态由false改为true，导致这个单例的handler无法再被使用
 * 因此需要加上@Sharable注解才能通用这个单例
 */
@Component
@ChannelHandler.Sharable
public class TCPServerHandler extends ChannelInboundHandlerAdapter {

    private Logger logger = LoggerFactory.getLogger(TCPServerHandler.class);

    private final SessionManager sessionManager;

    private final MsgDecoder decoder;

    private TerminalMsgProcessService msgProcessService;

    private JT808ProtocolUtils protocolUtils = new JT808ProtocolUtils();

    public TCPServerHandler() {
        this.sessionManager = SessionManager.getInstance();
        this.decoder = new MsgDecoder();
        this.msgProcessService = new TerminalMsgProcessService();
    }

    /**
     * 这两个接口也是单例的，伴随着第一个创建的handler创建，
     * 必须保证handler被共用，不然之后会报空指针
     */
    @Resource
    private DqVehicleInfoMapper dqVehicleInfoMapper;
    @Resource
    private DqJt808LocationInfoMapper dqJt808LocationInfoMapper;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            ByteBuf buf = (ByteBuf) msg;
            if (buf.readableBytes() <= 0) {
//                ReferenceCountUtil.safeRelease(msg);
                return;
            }
            byte[] bs = new byte[buf.readableBytes()];
            buf.readBytes(bs);

            bs = this.protocolUtils.doEscape4Receive(bs, 0, bs.length);
            // 字节数据转换为针对于808消息结构的实体类
            PackageData pkg = this.decoder.bytes2PackageData(bs);
            // 引用channel,以便回送数据给硬件
            pkg.setChannel(ctx.channel());
            this.processPackageData(pkg, ctx);
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            release(msg);
        }
    }

    /**
     * 处理业务逻辑
     *
     * @param packageData
     */
    private void processPackageData(PackageData packageData, ChannelHandlerContext ctx) {

        final PackageData.MsgHeader header = packageData.getMsgHeader();

        // 1. 终端心跳-消息体为空 ==> 平台通用应答
        if (TPMSConsts.msg_id_terminal_heart_beat == header.getMsgId()) {
            ChannelId channelId = ctx.channel().id();
            if (!CommonConstant.VEHICLE_MAP.containsKey(header.getTerminalPhone())) {
                logger.info("当前有新连接接入:" + header.getTerminalPhone());
                CommonConstant.VEHICLE_MAP.put(header.getTerminalPhone(), channelId);
                dqVehicleInfoMapper.updateStateByTerminalPhone(header.getTerminalPhone(), 0);
                logger.info("当前连接总数:" + CommonConstant.VEHICLE_MAP.size());
            }
            try {
                this.msgProcessService.processTerminalHeartBeatMsg(packageData);
            } catch (Exception e) {
                logger.error("<<<<<[终端心跳]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }

        // 5. 终端鉴权 ==> 平台通用应答
        else if (TPMSConsts.msg_id_terminal_authentication == header.getMsgId()) {
//            logger.info(">>>>>[终端鉴权],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            try {
                TerminalAuthenticationMsg authenticationMsg = new TerminalAuthenticationMsg(packageData);
                this.msgProcessService.processAuthMsg(authenticationMsg);
            } catch (Exception e) {
                logger.error("<<<<<[终端鉴权]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 6. 终端注册 ==> 终端注册应答
        else if (TPMSConsts.msg_id_terminal_register == header.getMsgId()) {
//            logger.info(">>>>>[终端注册],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            try {
                TerminalRegisterMsg msg = this.decoder.toTerminalRegisterMsg(packageData);
                this.msgProcessService.processRegisterMsg(msg);
//                logger.info("<<<<<[终端注册],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            } catch (Exception e) {
                logger.error("<<<<<[终端注册]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 7. 终端注销(终端注销数据消息体为空) ==> 平台通用应答
        else if (TPMSConsts.msg_id_terminal_log_out == header.getMsgId()) {
            try {
                this.msgProcessService.processTerminalLogoutMsg(packageData);
//                logger.info("<<<<<[终端注销],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            } catch (Exception e) {
                logger.error("<<<<<[终端注销]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 3. 位置信息汇报 ==> 平台通用应答
        else if (TPMSConsts.msg_id_terminal_location_info_upload == header.getMsgId()) {
            try {
                LocationInfoUploadMsg locationInfoUploadMsg = this.decoder.toLocationInfoUploadMsg(packageData);
                DqJt808LocationInfo locationInfo = new DqJt808LocationInfo();
                locationInfo.setCreateTime(new Date());
                locationInfo.setPhone(header.getTerminalPhone());
                locationInfo.setLatitude(locationInfoUploadMsg.getLatitude());
                locationInfo.setLongitude(locationInfoUploadMsg.getLongitude());
                locationInfo.setSpeed(locationInfoUploadMsg.getSpeed());
                locationInfo.setStatusField(locationInfoUploadMsg.getStatusField());
                locationInfo.setWarningFlagField(locationInfoUploadMsg.getWarningFlagField());
                dqJt808LocationInfoMapper.insertSelective(locationInfo);
                this.msgProcessService.processLocationInfoUploadMsg(locationInfoUploadMsg);
            } catch (Exception e) {
                logger.error("<<<<<[位置信息]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 其他情况
        else {
            logger.error(">>>>>>[未知消息类型],phone={},msgId={},package={}", header.getTerminalPhone(), header.getMsgId(),
                    packageData);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Session session = Session.buildSession(ctx.channel());
        sessionManager.put(session.getId(), session);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        final String sessionId = ctx.channel().id().asLongText();
        this.sessionManager.removeBySessionId(sessionId);
        ctx.channel().close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                Session session = this.sessionManager.removeBySessionId(Session.buildId(ctx.channel()));
                logger.error("服务器主动断开连接:{}", session);
                ctx.close();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("发生异常:{}", cause.getMessage());
        cause.printStackTrace();
    }

    private void release(Object msg) {
        try {
            ReferenceCountUtil.release(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 每隔15分钟将不回传数据的车载在线状态改为离线
     */
    @Scheduled(cron = "0 0/15 5-22 * * ?")
    public void schedulingVehicleInfoState() {
        List<VehicleInDataTimeDto> list = dqJt808LocationInfoMapper.selectVehicleTime();
        logger.info("修改车载在线状态开始");
        for (VehicleInDataTimeDto dataTimeDto : list) {
            for (String terminalPhone : CommonConstant.VEHICLE_MAP.keySet()) {
                if (dataTimeDto.getPhone().equals(terminalPhone)) {
                    Date date = dataTimeDto.getCreateTime();
                    Date now = new Date();
                    double minutes = now.getTime() - date.getTime();
                    BigDecimal result = new BigDecimal(minutes).divide(new BigDecimal(1000 * 60), 1, BigDecimal.ROUND_HALF_UP);
                    if (result.compareTo(BigDecimal.valueOf(15.0)) == 1) {//判断是否大于15分钟的停留时间
                        dqVehicleInfoMapper.updateStateByTerminalPhone(terminalPhone, 1);
                        CommonConstant.VEHICLE_MAP.remove(terminalPhone);
                    }
                }
            }
        }
        logger.info("修改车载在线状态结束");
    }
}
